/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace CsJni
{
    [Serializable]
    public class JniThrowableException : Exception, ISerializable
    {
        private JniThrowable mThrowable;

        public JniThrowableException()
        {
        }

        public JniThrowableException(String message) : base(message)
        {
        }

        public JniThrowableException(string message, Exception inner)
            : base(message, inner)
        {
        }

        protected JniThrowableException(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }

        public JniThrowable Throwable
        {
            get { return mThrowable; }
            internal set { mThrowable = value; }
        }

        #region ISerializable Members

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }

        #endregion
    }

    public sealed class JniThrowable : JniObject
    {
        public static void CheckException(JniEnvironment environment)
        {
            if (JniThrowable.ExceptionCheck(environment))
            {
                ThrowCurrentException(environment);
            }
        }

        internal JniThrowableException CreateThrowableException(JniEnvironment environment)
        {
            JniThrowableException ex = new JniThrowableException(this.ExtractStackTrace(environment));
            ex.Throwable = this;
            return ex;
        }

        internal static void ThrowCurrentException(JniEnvironment environment)
        {
            IntPtr handle = JniThrowable.ExceptionOccurred(environment);
            JniThrowable.ExceptionClear(environment);
            JniThrowable throwable = (JniThrowable)(new JniThrowable(environment,
                handle, JniObjectLifecycle.Local).NewGlobalAutoReleaseAndReleaseThis(environment)); // TODO
            throw throwable.CreateThrowableException(environment);
        }

        internal JniThrowable(JniEnvironment environment, IntPtr handle, JniObjectLifecycle lifecycle)
            : base(environment, handle, lifecycle)
        {
        }

        public override JniConcreteObjectType ConcreteType
        {
            get { return JniConcreteObjectType.Throwable; }
        }

        public string ExtractStackTrace(JniEnvironment environment)
        {
            string message = JniResourceManager.Instance.GetString("jniExceptionCheckFailed") +
                Environment.NewLine +
                ExtractStackTrace(environment, this);
            return message;
        }

        public void ThrowToJavaVM(JniEnvironment environment)
        {
            JniObject.VerifyInputObject(environment);
            int result = JniEnvironment.Functions.Throw(environment.Handle, this.Handle);
            if (0 != result)
            {
                JniException.CheckErrorCode(environment, result);
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
        }

        // OK to suppress, match JNI name & signature
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public static void ThrowNewToJavaVM(
            JniEnvironment environment,
            JniClass jniClass,
            string message
            )
        {
            VerifyInputJniObject(jniClass);
            int result = JniEnvironment.Functions.ThrowNew(environment.Handle, jniClass.Handle, message);
            if (0 != result)
            {
                JniException.CheckErrorCode(environment, result);
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
        }

        public static void ThrowDotNetExceptionToJavaVM(JniEnvironment environment, Exception exception)
        {
            // TODO - match common .NET exceptions to correspoding Java exceptions
            JniClass jniClass = JniClass.FindClass(environment, "csjni/DotNetException");
            ThrowNewToJavaVM(environment, jniClass, ExtractDotNetExceptionMessage(exception));
        }

        public static string ExtractDotNetExceptionMessage(Exception exception)
        {
            return exception.ToString(); // TODO - walk inner exceptions, get stack traces
        }

        public static bool ExceptionCheck(JniEnvironment environment)
        {
            byte result = JniEnvironment.Functions.ExceptionCheck(environment.Handle);
            // EXCEPTION CHECK: Do nothing, this method safe to call while exception is pending, does
            // not raise additional exceptions.
            return JBooleanConverter.From(result);
        }

        private static IntPtr ExceptionOccurred(JniEnvironment environment)
        {
            IntPtr result = JniEnvironment.Functions.ExceptionOccurred(environment.Handle);
            // EXCEPTION CHECK: Do nothing, this method safe to call while exception is pending, does
            // not raise additional exceptions.
            return result;
        }

        public static void ExceptionClear(JniEnvironment environment)
        {
            JniEnvironment.Functions.ExceptionClear(environment.Handle);
            // EXCEPTION CHECK: Do nothing, this method safe to call while exception is pending, does
            // not raise additional exceptions.
        }

        internal static string ExtractStackTrace(JniEnvironment environment, JniThrowable throwableObj)
        {
            JniLocalFrame frame = environment.CreateLocalFrame(10);
            try
            {
                // construct ByteArrayOutputStream
                JniClass baosClazz = JniClass.FindClass(environment, "java/io/ByteArrayOutputStream");
                JniConstructor baosCtorMid = baosClazz.GetConstructorId(environment, "()V");
                JniMethod baosFlushMid = baosClazz.GetMethodId(environment, "flush", "()V");
                JniMethod baosToStringMid = baosClazz.GetMethodId(environment, "toString", "()Ljava/lang/String;");
                JniObject baosObj = baosCtorMid.NewObject(environment, null);

                // construct PrintStream
                JniClass psClazz = JniClass.FindClass(environment, "java/io/PrintStream");
                JniConstructor psCtorMid = psClazz.GetConstructorId(environment, "(Ljava/io/OutputStream;)V");
                JValue[] ps_paramArray = new JValue[1];
                ps_paramArray[0].AssignObjectValue(baosObj);
                JniObject psObj = psCtorMid.NewObject(environment, ps_paramArray);

                // call printStackTrace
                JniClass throwableClazz = throwableObj.GetObjectClass(environment);
                JniMethod throwablePSTMid = throwableClazz.GetMethodId(environment, "printStackTrace", "(Ljava/io/PrintStream;)V");
                JValue[] t_paramArray = new JValue[1];
                t_paramArray[0].AssignObjectValue(psObj);
                throwablePSTMid.CallVoidMethod(environment, throwableObj, t_paramArray);

                // flush ByteArrayOutputStream & get string
                baosFlushMid.CallVoidMethod(environment, baosObj, null);
                string stackTrace = baosToStringMid.CallStringMethod(environment, baosObj, null);

                return stackTrace;
            }
            finally
            {
                frame.SafePop();
            }
        }
    }
}
